import type { ASTNode, print } from "graphql";
import { stripIgnoredCharacters } from "graphql";
import { gql } from "graphql-tag";

import {
  fallbackHttpConfig,
  selectHttpOptionsAndBody,
  selectHttpOptionsAndBodyInternal,
} from "@apollo/client/link/http";
import { createOperationWithDefaultContext as createOperation } from "@apollo/client/testing/internal";

const query = gql`
  query SampleQuery {
    stub {
      id
    }
  }
`;

describe("selectHttpOptionsAndBody", () => {
  it("includeQuery allows the query to be ignored", () => {
    const { body } = selectHttpOptionsAndBody(createOperation({ query }), {
      http: { includeQuery: false },
    });
    expect(body).not.toHaveProperty("query");
  });

  it("includeExtensions allows the extensions to be added", () => {
    const extensions = { yo: "what up" };
    const { body } = selectHttpOptionsAndBody(
      createOperation({ query, extensions }),
      { http: { includeExtensions: true } }
    );
    expect(body).toHaveProperty("extensions");
    expect((body as any).extensions).toEqual(extensions);
  });

  it("the fallbackConfig is used if no other configs are specified", () => {
    const defaultHeaders = {
      accept: "application/graphql-response+json,application/json;q=0.9",
      "content-type": "application/json",
    };

    const defaultOptions = {
      method: "POST",
    };

    const extensions = { yo: "what up" };
    const { options, body } = selectHttpOptionsAndBody(
      createOperation({ query, extensions }),
      fallbackHttpConfig
    );

    expect(body).toHaveProperty("query");
    expect(body.extensions).toStrictEqual(extensions);

    expect(options.headers).toEqual(defaultHeaders);
    expect(options.method).toEqual(defaultOptions.method);
  });

  it("allows headers, credentials, and setting of method to function correctly", () => {
    const headers = {
      accept: "application/json",
      "content-type": "application/graphql",
    };

    const credentials = {
      "X-Secret": "djmashko",
    };

    const opts = {
      opt: "hi",
    };

    const config = { headers, credentials, options: opts };

    const extensions = { yo: "what up" };

    const { options, body } = selectHttpOptionsAndBody(
      createOperation({ query, extensions }),
      fallbackHttpConfig,
      config
    );

    expect(body).toHaveProperty("query");
    expect(body.extensions).toStrictEqual(extensions);

    expect(options.headers).toEqual(headers);
    expect(options.credentials).toEqual(credentials);
    expect(options.opt).toEqual("hi");
    expect(options.method).toEqual("POST"); //from default
  });

  it("can explicitly disable `includeExtensions`", () => {
    const headers = {
      accept: "application/json",
      "content-type": "application/graphql",
    };

    const credentials = {
      "X-Secret": "djmashko",
    };

    const extensions = { yo: "what up" };

    const { options, body } = selectHttpOptionsAndBody(
      createOperation({ query, extensions }),
      fallbackHttpConfig,
      {
        headers,
        credentials,
        http: {
          includeExtensions: false,
        },
        options: { opt: "hi" },
      }
    );

    expect(body).toHaveProperty("query");
    expect(body).not.toHaveProperty("extensions");

    expect(options.headers).toEqual(headers);
    expect(options.credentials).toEqual(credentials);
    expect(options.opt).toEqual("hi");
    expect(options.method).toEqual("POST"); //from default
  });

  it("applies custom printer function when provided", () => {
    const customPrinter = (ast: ASTNode, originalPrint: typeof print) => {
      return stripIgnoredCharacters(originalPrint(ast));
    };

    const { body } = selectHttpOptionsAndBodyInternal(
      createOperation({ query }),
      customPrinter,
      fallbackHttpConfig
    );

    expect(body.query).toBe("query SampleQuery{stub{id}}");
  });
});
